package me.zhengjie.modules.weixin.utils;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.naming.NoNameCoder;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import me.zhengjie.config.constant.WeChatConstant;
import me.zhengjie.config.exception.WeChatException;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.lang.reflect.Field;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 微信工具类
 */
public enum WeChatUtil {
    INSTANCE;

    private static XStream xStream;

    public XStream getXStream(boolean forceInit) {
        if (forceInit) {
            return getInitXStream();
        } else {
            return getXStream();
        }
    }

    public XStream getInitXStream() {
        return new XStream(new XppDriver(new NoNameCoder()) {
            @Override
            public HierarchicalStreamWriter createWriter(Writer out) {

                return new PrettyPrintWriter(out) {
                    @Override
                    @SuppressWarnings("rawtypes")
                    public void startNode(String name, Class clazz) {
                        super.startNode(name, clazz);
                    }

                    @Override
                    public String encodeNode(String name) {
                        return name;
                    }

                    @Override
                    protected void writeText(QuickWriter writer, String text) {
                        if (!StringUtils.isNumeric(text)) {
                            writer.write("<![CDATA[");
                            writer.write(text);
                            writer.write("]]>");
                        } else {
                            writer.write(text);
                        }
                    }
                };
            }
        });
    }

    public XStream getXStream() {
        if (null == xStream) {
            xStream = getInitXStream();
        }
        return xStream;
    }

    public String toXML(Object obj) {
        return toXML("xml", obj);
    }

    public String toXML(String alias, Object obj) {
        getXStream().alias(alias, obj.getClass());
        return getXStream().toXML(obj);
    }

    public <T> T fromXML(String xml) {
        return (T) getXStream().fromXML(xml);
    }


    public String createNonceStr() {
        return RandomStringUtils.random(16, WeChatConstant.CHAR_POOL);
    }

    public String createSalt() {
        return RandomStringUtils.random(8, WeChatConstant.CHAR_POOL);
    }

    public String createTimestamp() {
        return Long.toString(getTimestamp());
    }

    public long getTimestamp() {
        return System.currentTimeMillis() / 1000;
    }

    public String convertInputStream2String(InputStream in) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        StringBuilder sb = new StringBuilder();
        String str;
        while ((str = br.readLine()) != null) {
            sb.append(str);
        }
        return sb.toString();
    }

    public Map<String, Object> convertXml2Map(String xml) throws IOException {
        Map<String, Object> maps = new HashMap<>();
        convertXml2Map(xml, maps);
        return maps;
    }

    private void convertXml2Map(String xml, Map<String, Object> maps) {
        try {
            Document document = DocumentHelper.parseText(xml);
            Element root = document.getRootElement();
            List<Element> eles = (List<Element>) root.elements();
            for (Element e : eles) {
                if (e.isTextOnly()) {
                    maps.put(e.getName(), e.getTextTrim());
                } else {
                    maps.put(e.getName(), e.getStringValue());
                    convertXml2Map(e.asXML(), maps);
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    public <T> T convertMap2Bean(Map<String, Object> source, Class<T> instance) {
        try {
            T object = instance.newInstance();
            Field[] fields = object.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                field.set(object, source.get(field.getName()));
            }
            return object;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String buildSignature(String... encrypt) {
        Arrays.sort(encrypt);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < encrypt.length; i++) {
            sb.append(encrypt[i]);
        }
        return getSHA1(sb.toString());
    }

    public Map<String, String> jsApiSign(String jsApiTicket, String url) {
        Map<String, String> ret = new HashMap<>(8);
        String nonceStr = createNonceStr();
        String timestamp = createTimestamp();

        // 注意这里参数名必须全部小写，且必须有序
        String string1 = MessageFormat.format(WeChatConstant.JSAPI_SIGN, jsApiTicket, nonceStr, timestamp, url);

        String signature = getSHA1(string1);

        ret.put("url", url);
        ret.put("nonceStr", nonceStr);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);

        return ret;
    }

    public static String getSHA1(String str) throws WeChatException {
        try {
            // SHA1签名生成
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(str.getBytes());
            byte[] digest = md.digest();

            StringBuilder hexstr = new StringBuilder();
            String shaHex;
            for (int i = 0; i < digest.length; i++) {
                shaHex = Integer.toHexString(digest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexstr.append(0);
                }
                hexstr.append(shaHex);
            }
            return hexstr.toString();
        } catch (Exception e) {
            e.printStackTrace();
            throw new WeChatException(WeChatException.ComputeSignatureError);
        }
    }

    public SSLConnectionSocketFactory createSSLConnSocketFactory() {
        SSLConnectionSocketFactory sslsf = null;
        try {
            SSLContext ctx = SSLContext.getInstance("SSL");
            X509TrustManager tm = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };
            ctx.init(null, new TrustManager[]{tm}, null);
            sslsf = new SSLConnectionSocketFactory(ctx, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
        return sslsf;
    }

}
